package com.ideaaedi.zoo.example.springboot.flowable.sharding.service.impl;

import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ideaaedi.zoo.diy.artifact.flowable.facade.*;
import com.ideaaedi.zoo.example.springboot.flowable.sharding.constant.DataSourceName;
import com.ideaaedi.zoo.example.springboot.flowable.sharding.entity.dto.*;
import com.ideaaedi.zoo.example.springboot.flowable.sharding.entity.po.ActHiVarinstPO;
import com.ideaaedi.zoo.example.springboot.flowable.sharding.entity.tuple.StrObjVar;
import com.ideaaedi.zoo.example.springboot.flowable.sharding.mapper.*;
import com.ideaaedi.zoo.example.springboot.flowable.sharding.service.ActHiVarinstService;
import com.ideaaedi.zoo.example.springboot.flowable.sharding.service.ShardingBpmnHistoryService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Nonnull;
import java.util.*;
import java.util.stream.Collectors;

/**
 * (non-javadoc)
 *
 * @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img
 * src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
 * @since 1.0.0
 */
@Slf4j
@Service
public class ShardingBpmnHistoryServiceImpl implements ShardingBpmnHistoryService {
    
    @Resource
    private ActHiProcinstMapper actHiProcinstMapper;
    @Resource
    private ActHiTaskinstMapper actHiTaskinstMapper;
    @Resource
    private ActHiCommentMapper actHiCommentMapper;
    @Resource
    private ActHiAttachmentMapper actHiAttachmentMapper;
    @Resource
    private ActHiVarinstMapper actHiVarinstMapper;
    @Resource
    private ActHiVarinstService actHiVarinstService;
    
    @Override
    @DS(DataSourceName.SHARDING)
    public DefaultBpmnHisProcessInstance detailHisProcessInstance(@Nonnull ShardingProcessDetailQueryDTO param) {
        return actHiProcinstMapper.detailHisProcessInstance(param);
    }
    
    @Override
    @DS(DataSourceName.SHARDING)
    public List<DefaultBpmnHisProcessInstance> listHisProcessInstance(@Nonnull ShardingProcessListQueryDTO param) {
        Collection<String> processInstanceIds = param.getProcessInstanceIds();
        Collection<StrObjVar> processVariableValueEqualsColl = param.getProcessVariableValueEqualsColl();
        String shardingDateFrom = param.getShardingDateFrom();
        String shardingDateTo = param.getShardingDateTo();
        
        // step1. 从变量中查询满足的流程实例id
        Set<String> procInstIdSet = null;
        if (!CollectionUtils.isEmpty(processVariableValueEqualsColl)) {
            ShardingVarListQueryDTO varListQuery = ShardingVarListQueryDTO.builder()
                    .shardingDateFrom(shardingDateFrom)
                    .shardingDateTo(shardingDateTo)
                    .tenant(param.getTenant())
                    .processInstanceIds(processInstanceIds == null ? null : new HashSet<>(processInstanceIds))
                    .orVarEqualsColl(VarQueryParam.ofColl(false, processVariableValueEqualsColl))
                    .build();
            List<ActHiVarinstPO> actHiVarinstList = actHiVarinstMapper.listVar(varListQuery);
            if (CollectionUtils.isEmpty(actHiVarinstList)) {
                return Collections.emptyList();
            }
            procInstIdSet = new HashSet<>();
            Map<String, List<ActHiVarinstPO>> procInstIdAndVarListMap = actHiVarinstList.stream()
                    .collect(Collectors.groupingBy(ActHiVarinstPO::getProcInstId));
            Set<StrObjVar> procVarSet = new HashSet<>(processVariableValueEqualsColl);
            int procVarMinSize = procVarSet.size();
            for (Map.Entry<String, List<ActHiVarinstPO>> entry : procInstIdAndVarListMap.entrySet()) {
                String procInstId = entry.getKey();
                List<ActHiVarinstPO> varList = entry.getValue();
                // 查询出来的 变量总数、局部变量总数、流程变量总数
                long procVarSize = varList.stream().filter(x -> x.getTaskId() == null).count();
                if (procVarSize >= procVarMinSize) {
                    procInstIdSet.add(procInstId);
                }
            }
        }
        if (procInstIdSet != null) {
            if (procInstIdSet.size() == 0) {
                return Collections.emptyList();
            }
            param.setProcessInstanceIds(procInstIdSet);
        }
        
        // step2. 查询流程
        List<DefaultBpmnHisProcessInstance> list = actHiProcinstMapper.listHisProcessInstance(param);
        if (CollectionUtils.isEmpty(list)) {
            return list;
        }
        
        // step3. 查询变量
        boolean queryProcessVariables = BooleanUtils.isTrue(param.getQueryProcessVariables());
        if (queryProcessVariables) {
            // 查询流程变量
            Set<String> processInstanceIdSet =
                    list.stream().map(BpmnHisProcessInstance::getId).collect(Collectors.toSet());
            Map<String, List<ActHiVarinstPO>> proInsIdAndVariableListMap =
                    actHiVarinstService.list(Wrappers.lambdaQuery(ActHiVarinstPO.class)
                            .ge(ActHiVarinstPO::getShardingDate, shardingDateFrom)
                            .le(ActHiVarinstPO::getShardingDate, shardingDateTo)
                            .isNull(ActHiVarinstPO::getTaskId)
                            .in(ActHiVarinstPO::getProcInstId, processInstanceIdSet)
                    ).stream().collect(Collectors.groupingBy(ActHiVarinstPO::getProcInstId));
            // 组装变量
            for (DefaultBpmnHisProcessInstance hisProc : list) {
                List<ActHiVarinstPO> actHiVarinstList = proInsIdAndVariableListMap.get(hisProc.getId());
                if (CollectionUtils.isEmpty(actHiVarinstList)) {
                    continue;
                }
                Map<String, Object> processVariables = new HashMap<>(16);
                for (ActHiVarinstPO actHiVarinst : actHiVarinstList) {
                    String procInstId = actHiVarinst.getProcInstId();
                    String name = actHiVarinst.getName();
                    Object value = ObjectUtils.firstNonNull(
                            actHiVarinst.getText(),
                            actHiVarinst.getLongVal(),
                            actHiVarinst.getDoubleVal(),
                            actHiVarinst.getBytearrayId()
                    );
                    if (value == null) {
                        log.warn("variable value is null. curr variable name -> {}, curr procInstId -> {}", name,
                                procInstId);
                        continue;
                    }
                    processVariables.put(name, value);
                }
                hisProc.setProcessVariables(processVariables);
            }
        }
        return list;
    }
    
    @Override
    @DS(DataSourceName.SHARDING)
    public List<DefaultBpmnHisTask> listHisTask(ShardingTaskListQueryDTO param) {
        String processInstanceId = param.getProcessInstanceId();
        Set<String> processInstanceIds = param.getProcessInstanceIds();
        Set<String> taskIds = param.getTaskIds();
        String shardingDateFrom = param.getShardingDateFrom();
        String shardingDateTo = param.getShardingDateTo();
        Collection<StrObjVar> taskVariableValueEqualsColl = param.getTaskVariableValueEqualsColl();
        Collection<StrObjVar> processVariableValueEqualsColl = param.getProcessVariableValueEqualsColl();
        boolean taskVarConditionIsEmpty = CollectionUtils.isEmpty(taskVariableValueEqualsColl);
        boolean processVarConditionIsEmpty = CollectionUtils.isEmpty(processVariableValueEqualsColl);
        
        // step1. 从变量中查询满足的流程实例id
        Set<String> procInstIdSet = null;
        if (!taskVarConditionIsEmpty || !processVarConditionIsEmpty) {
            List<VarQueryParam> orVarEqualsColl = new ArrayList<>();
            orVarEqualsColl.addAll(VarQueryParam.ofColl(true, taskVariableValueEqualsColl));
            orVarEqualsColl.addAll(VarQueryParam.ofColl(false, processVariableValueEqualsColl));
            ShardingVarListQueryDTO varListQuery = ShardingVarListQueryDTO.builder()
                    .shardingDateFrom(shardingDateFrom)
                    .shardingDateTo(shardingDateTo)
                    .tenant(param.getTenant())
                    .processInstanceId(processInstanceId)
                    .processInstanceIds(processInstanceIds)
                    .processByTaskIds(taskIds)
                    .orVarEqualsColl(orVarEqualsColl)
                    .build();
            List<ActHiVarinstPO> actHiVarinstList = actHiVarinstMapper.listVar(varListQuery);
            if (CollectionUtils.isEmpty(actHiVarinstList)) {
                return Collections.emptyList();
            }
            procInstIdSet = new HashSet<>();
            Map<String, List<ActHiVarinstPO>> procInstIdAndVarListMap = actHiVarinstList.stream()
                    .collect(Collectors.groupingBy(ActHiVarinstPO::getProcInstId));
            Set<StrObjVar> taskVarSet = new HashSet<>();
            Set<StrObjVar> procVarSet = new HashSet<>();
            if (!taskVarConditionIsEmpty) {
                taskVarSet.addAll(taskVariableValueEqualsColl);
            }
            if (!processVarConditionIsEmpty) {
                procVarSet.addAll(processVariableValueEqualsColl);
            }
            int taskVarMinSize = taskVarSet.size();
            int procVarMinSize = procVarSet.size();
            for (Map.Entry<String, List<ActHiVarinstPO>> entry : procInstIdAndVarListMap.entrySet()) {
                String procInstId = entry.getKey();
                List<ActHiVarinstPO> varList = entry.getValue();
                // 查询出来的 变量总数、局部变量总数、流程变量总数
                int totalVarSize = varList.size();
                long procVarSize = varList.stream().filter(x -> x.getTaskId() == null).count();
                long taskVarSize = totalVarSize - procVarSize;
                if (taskVarSize >= taskVarMinSize && procVarSize >= procVarMinSize) {
                    procInstIdSet.add(procInstId);
                }
            }
        }
        if (procInstIdSet != null) {
            if (procInstIdSet.size() == 0) {
                return Collections.emptyList();
            }
            param.setProcessInstanceIds(procInstIdSet);
        }
        
        // step2. 任务
        List<DefaultBpmnHisTask> list = actHiTaskinstMapper.listHisTask(param);
        if (CollectionUtils.isEmpty(list)) {
            return list;
        }
        
        // step3. 变量
        boolean queryTaskLocalVariables = BooleanUtils.isTrue(param.getQueryTaskLocalVariables());
        boolean queryProcessVariables = BooleanUtils.isTrue(param.getQueryProcessVariables());
        if (queryTaskLocalVariables || queryProcessVariables) {
            // 查询变量
            Set<String> processInstanceIdSet =
                    list.stream().map(DefaultBpmnHisTask::getProcessInstanceId).collect(Collectors.toSet());
            Map<String, List<ActHiVarinstPO>> proInsIdAndVariableListMap =
                    actHiVarinstService.list(Wrappers.lambdaQuery(ActHiVarinstPO.class)
                    .ge(ActHiVarinstPO::getShardingDate, shardingDateFrom)
                    .le(ActHiVarinstPO::getShardingDate, shardingDateTo)
                    .in(ActHiVarinstPO::getProcInstId, processInstanceIdSet)
            ).stream().collect(Collectors.groupingBy(ActHiVarinstPO::getProcInstId));
            // 组装变量
            for (DefaultBpmnHisTask hisTask : list) {
                List<ActHiVarinstPO> actHiVarinstList = proInsIdAndVariableListMap.get(hisTask.getProcessInstanceId());
                if (CollectionUtils.isEmpty(actHiVarinstList)) {
                    continue;
                }
                Map<String, Object> taskLocalVariables = new HashMap<>(16);
                Map<String, Object> processVariables = new HashMap<>(16);
                for (ActHiVarinstPO actHiVarinst : actHiVarinstList) {
                    String procInstId = actHiVarinst.getProcInstId();
                    String name = actHiVarinst.getName();
                    Object value = ObjectUtils.firstNonNull(
                            actHiVarinst.getText(),
                            actHiVarinst.getLongVal(),
                            actHiVarinst.getDoubleVal(),
                            actHiVarinst.getBytearrayId()
                    );
                    if (value == null) {
                        log.warn("variable value is null. curr variable name -> {}, curr procInstId -> {}", name,
                                procInstId);
                        continue;
                    }
                    if (Objects.isNull(actHiVarinst.getTaskId())) {
                        processVariables.put(name, value);
                    } else {
                        taskLocalVariables.put(name, value);
                    }
                }
                if (queryTaskLocalVariables) {
                    hisTask.setTaskLocalVariables(taskLocalVariables);
                }
                if (queryProcessVariables) {
                    hisTask.setProcessVariables(processVariables);
                }
            }
        }
        return list;
    }
    
    @Override
    @DS(DataSourceName.SHARDING)
    public DefaultBpmnHisTask detailHisTask(ShardingTaskDetailQueryDTO param) {
        TaskWithVarDTO taskWithVar = actHiTaskinstMapper.detailHisTask(param);
        return buildDefaultBpmnHisTask(taskWithVar);
    }
    
    @Override
    @DS(DataSourceName.SHARDING)
    public List<DefaultBpmnComment> getProcessInstanceComments(ShardingCommentListQueryDTO param) {
        return actHiCommentMapper.getProcessInstanceComments(param);
    }
    
    @Override
    @DS(DataSourceName.SHARDING)
    public List<DefaultBpmnAttachment> getProcessInstanceAttachments(ShardingAttachmentListQueryDTO param) {
        return actHiAttachmentMapper.getProcessInstanceAttachments(param);
    }
    
    /**
     * 构建DefaultBpmnHisTask对象
     */
    private static DefaultBpmnHisTask buildDefaultBpmnHisTask(TaskWithVarDTO taskWithVar) {
        DefaultBpmnHisTask defaultBpmnHisTask = new DefaultBpmnHisTask();
        defaultBpmnHisTask.setId(taskWithVar.getParentTaskId());
        defaultBpmnHisTask.setName(taskWithVar.getName());
        defaultBpmnHisTask.setDescription(taskWithVar.getDescription());
        defaultBpmnHisTask.setPid(taskWithVar.getParentTaskId());
        defaultBpmnHisTask.setCategory(taskWithVar.getCategory());
        defaultBpmnHisTask.setTenant(taskWithVar.getTenant());
        defaultBpmnHisTask.setState(taskWithVar.getState());
        defaultBpmnHisTask.setDueDate(taskWithVar.getDueDate());
        defaultBpmnHisTask.setCreateTime(taskWithVar.getEndTime());
        defaultBpmnHisTask.setOwner(taskWithVar.getOwner());
        defaultBpmnHisTask.setAssignee(taskWithVar.getAssignee());
        defaultBpmnHisTask.setProcessInstanceId(taskWithVar.getProcInstId());
        defaultBpmnHisTask.setProcessDefinitionId(taskWithVar.getProcDefId());
        defaultBpmnHisTask.setExecutionId(taskWithVar.getExecutionId());
        defaultBpmnHisTask.setClaimTime(taskWithVar.getClaimTime());
        defaultBpmnHisTask.setClaimedBy(taskWithVar.getClaimedBy());
        defaultBpmnHisTask.setSuspendedTime(taskWithVar.getSuspendedTime());
        defaultBpmnHisTask.setSuspendedBy(taskWithVar.getSuspendedBy());
        defaultBpmnHisTask.setDeleteReason(taskWithVar.getDeleteReason());
        defaultBpmnHisTask.setEndTime(taskWithVar.getEndTime());
        defaultBpmnHisTask.setCompletedBy(taskWithVar.getCompletedBy());
        defaultBpmnHisTask.setDurationInMillis(taskWithVar.getDuration());
        defaultBpmnHisTask.setWorkTimeInMillis(taskWithVar.getDuration());
        defaultBpmnHisTask.setTaskDefinitionKey(taskWithVar.getTaskDefKey());
        defaultBpmnHisTask.setFormKey(taskWithVar.getFormKey());
        
        Map<String, Object> taskLocalVariables = new HashMap<>(16);
        Map<String, Object> processVariables = new HashMap<>(16);
        List<ActHiVarinstDTO> variableList = taskWithVar.getVariableList();
        if (!CollectionUtils.isEmpty(variableList)) {
            for (ActHiVarinstDTO actHiVarinst : variableList) {
                String procInstId = actHiVarinst.getProcInstId();
                String name = actHiVarinst.getName();
                Object value = ObjectUtils.firstNonNull(
                        actHiVarinst.getText(),
                        actHiVarinst.getLongVal(),
                        actHiVarinst.getDoubleVal(),
                        actHiVarinst.getBytearrayId()
                );
                if (value == null) {
                    log.warn("variable value is null. curr variable name -> {}, curr procInstId -> {}", name,
                            procInstId);
                    continue;
                }
                if (Objects.isNull(taskWithVar.getId())) {
                    processVariables.put(name, value);
                } else {
                    taskLocalVariables.put(name, value);
                }
            }
        }
        defaultBpmnHisTask.setTaskLocalVariables(taskLocalVariables);
        defaultBpmnHisTask.setProcessVariables(processVariables);
        return defaultBpmnHisTask;
    }
}
